﻿using System;
using System.Collections.Generic;
using LogX.Logging;
using MMessaging.MessagingEx.Logger;
using MMessaging.MessagingEx.Common;
using TUtils.BasicHelper.Logging;
using TUtils.Threads;

namespace MMessaging.MessagingEx.Services
{
	/// <summary>
	/// Basisklasse für alle MMessaging-Services, deren Handler die WaitOnMessagexxx(..)
	/// Methoden aufrufen kann und darum quasi-parallel-läufige Vorgänge realisieren kann.
	/// </summary>
	/// <remarks>
	/// Abstrakte Basisklasse für alle MMessaging-Services, deren Handler die WaitOnMessagexxx(..)
	/// Methoden aufrufen kann und darum quasi-parallel-läufige Vorgänge realisieren kann. 
	/// MServiceBase wird bei der Initialisierung mit dem Ausgang eines unidirektionalen 
	/// Channels verbunden, der die Messages liefert, die vom Service verarbeitet werden 
	/// sollen. Die von MServiceBase abgeleitete Klasse muss die abstrakte Methode 
	/// OnMessage(..) implementieren, den eigentlichen Message-Handler. 
	/// Außerdem muss man die abstrakte Property LogImplementor  implementieren: 
	/// Hier hat man die Möglichkeit eine Service-spezifische Implementation des 
	/// Loggings zu realisieren, z.B. das Senden von Log-Messages. 
	/// Wenn die Property LogImplementor null zurückgibt, wird die projektspezifische 
	/// Standard-Implementation in LogXImplementor.dll für den Service verwendet.
	/// </remarks>
	/// <typeparam name="MESSAGETYPE"></typeparam>
	public abstract class MServiceBase<MESSAGETYPE> : IMService<MESSAGETYPE>
		where MESSAGETYPE : class
	{
		#region private

		private MMessageHandlerThread<MESSAGETYPE> _handler;
		private List<IMServicePlugIn<MESSAGETYPE>> _plugIns = new List<IMServicePlugIn<MESSAGETYPE>>();
		#endregion

		#region protected properties

		/// <summary>
		/// true, wenn Service beendet werden soll.
		/// Siehe OnMessage(..)
		/// </summary>
		public bool IsCanceling { get { return _handler.IsCanceling; } }

		protected void IfCurrentMessageIsOfType<CURRENTMSGTYPE>(
			IMTask<MESSAGETYPE> task,
			Action<CURRENTMSGTYPE> onTrue)
			where CURRENTMSGTYPE : class,MESSAGETYPE
		{
			var msg = task.CurrentMessage as CURRENTMSGTYPE;
			if (msg != null)
				onTrue(msg);
		}

		#endregion

		#region constructor

		public MServiceBase()
		{
			_handler = new MMessageHandlerThread<MESSAGETYPE>();
			_handler.SubTaskThreadSignalChanged += HandlerOnSubTaskThreadSignalChanged;
			_handler.ControllerThreadSignalChanged += HandlerOnControllerThreadSignalChanged;
		}

		private void HandlerOnControllerThreadSignalChanged(TThreadX<MMessageHandlerThread<MESSAGETYPE>>.IThreadContext context, TThreadX<MMessageHandlerThread<MESSAGETYPE>>.SignalEnum signal, Exception exception)
		{
			if (signal == TThreadX<MMessageHandlerThread<MESSAGETYPE>>.SignalEnum.STARTED)
				TLog.Instance.SetThreadAttachedLogValue(new LogValue(
					MPredefinedLoggingValueIDs.ServiceName,
					() => _handler.ThreadName));
		}

		private void HandlerOnSubTaskThreadSignalChanged(TThreadX<MTaskThread<MESSAGETYPE>>.IThreadContext context, TThreadX<MTaskThread<MESSAGETYPE>>.SignalEnum signal, Exception exception)
		{
			if (signal == TThreadX<MTaskThread<MESSAGETYPE>>.SignalEnum.STARTED)
				TLog.Instance.SetThreadAttachedLogValue(new LogValue(
					MPredefinedLoggingValueIDs.ServiceName,
					() => _handler.ThreadName));
		}

		/// <summary>
		/// Muss aufgerufen werden, um den Service zu initialisieren.
		/// </summary>
		/// <param name="messageHandlerName">
		/// Name des Service. Wird nur für Loggingzwecke genutzt.
		/// </param>
		/// <param name="channel">
		/// Ausgangsseite des Messagechannels, von dem der Service alle Messages
		/// erhält.
		/// </param>
		/// <param name="maxCountOfThreads">
		/// maximale Anzahl von Threads, die der Service erzeugen bzw. reservieren darf.
		/// Diese Anzahl ist abhängig von der maximalen Anzahl von fachlichen Abläufen,
		/// die theoretisch in der Serviceninstanz parallel ablaufen können. 
		/// </param>
		/// <param name="startAtOnce">
		/// true, wenn die Init() Methode den Service sofort starten soll.
		/// </param>
		/// <returns>this</returns>
		public MServiceBase<MESSAGETYPE> Init(
			string messageHandlerName,
			IMChannelEndOut<MESSAGETYPE> channel,
			int maxCountOfThreads,
			bool startAtOnce)
		{
			_handler.Init(
				messageHandlerName,
				channel,
				maxCountOfThreads,
				OnMessage);

			if (startAtOnce)
				StartAsync();

			return this;
		}

		public void AddMessageHandler(Action<IMTask<MESSAGETYPE>> messageHandler)
		{
			if (_handler != null)
				_handler.AddHandler(messageHandler);
		}

		public void RemoveMessageHandler(Action<IMTask<MESSAGETYPE>> messageHandler)
		{
			if (_handler != null)
				_handler.RemoveHandler(messageHandler);
		}

		#endregion

		#region abstract protected

		/// <summary>
		/// Messagehandler, der von der abgeleiteten Klasse implementiert werden muss.
		/// Die Methode darf nicht lange Zeit laufen, ohne task.WaitOnMessage..(..) oder 
		/// task.Sleep..(..) aufzurufen, da das sonst die 
		/// Pseudoparallelität der anderen Message-Verarbeitungsprozesse des Services
		/// behindern würde. Wenn der Service nicht mehrere parallel-läufige Vorgänge 
		/// gleichzeitig bearbeiten soll, kann von diesem Prinzip auch ausnahmsweise 
		/// abgewichen werden. In diesem Fall sollte aber die Property this.IsCanceling 
		/// regelmäßig abgefragt werden, um das saubere Herunterfahren der 
		/// Applikation bei Programmende zu gewährleisten.
		/// </summary>
		/// <param name="context"></param>
		protected abstract void OnMessage(IMTask<MESSAGETYPE> context);

		#endregion

		#region interface IMService<MESSAGETYPE>

		/// <summary>
		/// Muss aufgerufen werden, um den Service zu starten.
		/// Alternativ kann man auch in der Initialisierungsmethode den Parameter 
		/// "startAtOnce" setzen.
		/// StartAsync() darf nur aufgerufen werden, wenn der Service initialisiert worden
		/// ist (Aufruf von Init()).
		/// </summary>
		public virtual void StartAsync()
		{
			_handler.StartAsync();

		}

		/// <summary>
		/// Stoppt den Service asynchron.
		/// </summary>
		public virtual void CancelAsync()
		{
			_handler.CancelAsync();
		}

		public string ServiceName
		{
			get { return _handler.ThreadName; }
		}
		#endregion

		#region interface IDisposable

		public virtual void Dispose()
		{
			CancelAsync();
		}

		#endregion

		#region public

		// public event Action<IMTask<MESSAGETYPE>> MessageReceived = context => { };


		protected virtual void RegisterPlugIn(
			IMServicePlugIn<MESSAGETYPE> plugIn,
			Action<MESSAGETYPE> postMessageFunc)
		{
			_plugIns.Add(plugIn);
			plugIn.InitPlugIn(this, postMessageFunc);
		}

		#endregion
	}
}
